package org.ainlolcat.ainscience.entity;

import org.ainlolcat.Variable;
import org.ainlolcat.aincalc2.*;
import org.ainlolcat.ainscience.Controller;
import org.ainlolcat.ainscience.UpdateListener;
import org.ainlolcat.ainscience.column.Column;
import org.ainlolcat.ainscience.column.ColumnImpl;
import org.ainlolcat.ainscience.column.threshold.Threshold;
import org.ainlolcat.ainscience.column.threshold.ThresholdHelper;
import org.ainlolcat.ainscience.templates.Template;
import org.ainlolcat.ainscience.tools;
import org.ainlolcat.ainscience.utils.executor.Executor;
import org.ainlolcat.ainscience.utils.executor.ExecutorProvider;
import org.apache.log4j.Logger;
import org.jdom.Element;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @author ainlolcat
 * Date: 11/10/11
 */
public class EntityCalculator implements UpdateListener{
    private static Logger log = Logger.getLogger(EntityCalculator.class);
    Entity entity;
    List<Element> links;
    //stores Element representation of calculated columns
    List<Element> calcCols;
    Map<String, HashMap<String, List<Element>>> missings = new HashMap();
    /**
     * perform calculations on Columns. Uses links API so not safe in future
     * @param entity
     * @return
     */
    public Entity calculate(Entity entity){
        this.entity = entity;
        Template template = entity.getTemplate();
        Element parseInfo = template.getEntityParseParams(entity.getType());
        links = parseInfo.getChild("links")==null?
                                    new ArrayList<Element>():
                                    parseInfo.getChild("links").getChildren("link");
        calcCols = parseInfo.getChild("calculated")==null?
                                    new ArrayList<Element>():
                                    parseInfo.getChild("calculated").getChildren("column");
        for (Element elink:links){
            //TODO write for null case (user selection)
            if (entity.getLink(elink.getAttributeValue("name"))==null){

            }
        }
        for (Element ecolumn : calcCols){
            try {
                handleCalculatedElement(ecolumn);
            } catch (MissingDependenciesException md){
                if (missings.get(md.getEntity())==null)
                    missings.put(md.getEntity(), new HashMap<String, List<Element>>());
                if (missings.get(md.getEntity()).get(md.getColumn()) == null)
                    missings.get(md.getEntity()).put(md.getColumn(), new ArrayList<Element>());
                missings.get(md.getEntity()).get(md.getColumn()).add(ecolumn);
                Controller.getInstance().addUpdateListener(this);
            }
        }
        return entity;
    }

    private void handleCalculatedElement(Element ecolumn ){
        String name = ecolumn.getAttributeValue("name");
        String id = ecolumn.getAttributeValue("id");
        String axis = ecolumn.getAttributeValue("axis");
        String type = ecolumn.getAttributeValue("type");
        String formula = ecolumn.getAttributeValue("formula");
        if (formula!=null){
            checkDependencies(formula);

            ColumnImpl column = new ColumnImpl(name);
            column.setAxis(axis);
            column.setType(tools.parseInteger(type));

            new ThresholdHelper().searchThresholdAndApply(column, ecolumn);

            int rowCount = entity.getRowCount();
            List values = new ArrayList();

            Executor executor = ExecutorProvider.getExecutor(entity);
            for (int i=0;i<rowCount;i++){
                executor.getFunctionExecutor().setRow(i);
                try{
                    Object value =  executor.execute(formula).getStringValue();
                    values.add(value);
                } catch (Exception e) {
                    throw new RuntimeException("Cannot handle formula: " + formula + " on row " + i, e);
                }
            }
            column.setValues(values);
            entity.setColumn(column,tools.parseInteger(id));
        }
    }

    static Pattern selfValuePattern = Pattern.compile("val\\((\\w*)\\)");
    static Pattern valuesPattern = Pattern.compile("val\\((\\w*):\\d*\\)");
    private void checkDependencies(String formula){
        Matcher m = valuesPattern.matcher(formula);
        while (m.find()){
            if (entity.getLink(m.group(1)) == null){
                throw new MissingDependenciesException(m.group(1), m.group(2));
            }
        }
        m = selfValuePattern.matcher(formula);
        while (m.find()){
            String columnName = m.group(1);
            if (entity.getColumnByName(columnName) == null || entity.getColumnByName(columnName).size() == 0){
                throw new MissingDependenciesException(entity.getName(), m.group(1));
            }
        }
    }

    public void onProjectUpdate() {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    public void onEntityUpdate(Entity entity, int action) {
        log.debug("Update event income for entity " + entity.getName() + " while missing " + missings + " current column count " + entity.getColumnCount());
        boolean updated = false;
        if (missings.containsKey(entity.getName())){
            HashMap<String, List<Element>> waiting = missings.get(entity.getName());
            Iterator<String> iterator = waiting.keySet().iterator();
            while (iterator.hasNext()){
                String columnName = iterator.next();
                if (entity.getColumnByName(columnName) != null && entity.getColumnByName(columnName).size() > 0){
                    for (Element e: waiting.get(columnName)){
                        try{
                            handleCalculatedElement(e);
                        } catch (MissingDependenciesException md){
                            if (missings.get(md.getEntity())==null)
                                missings.put(md.getEntity(), new HashMap<String, List<Element>>());
                            if (missings.get(md.getEntity()).get(md.getColumn()) == null)
                                missings.get(md.getEntity()).put(md.getColumn(), new ArrayList<Element>());
                            missings.get(md.getEntity()).get(md.getColumn()).add(e);
                            Controller.getInstance().addUpdateListener(this);
                        }
                    }
                    iterator.remove();
                    updated = true;
                }
            }
        }
        if (updated)
            Controller.getInstance().throwUpdateEvent(entity, UPDATE_ENTITY_COLUMN);
    }
}
